/*
    Copyright (c) 2011 Denis Mingulov.

    This file is part of Password Generator.

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Foobar.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <meegotouch/MContainer>
#include <meegotouch/MLayout>
#include <meegotouch/MButton>
#include <meegotouch/MLabel>
#include <meegotouch/MSlider>
#include <meegotouch/MTextEdit>
#include <meegotouch/MComboBox>
#include <meegotouch/MContentItem>
#include <meegotouch/MMessageBox>
#include <meegotouch/MGridLayoutPolicy>
#include <meegotouch/MLinearLayoutPolicy>
#include <meegotouch/MLocale>
#include <QtCore/QSettings>
#include <QtCore/QPointer>
#include <QtGui/QGraphicsLinearLayout>
#include <QtGui/QGraphicsGridLayout>

#include <mssf-qt/MssfCrypto>

#include <QDebug>

#include "passgenwidget.h"

#include "translations.h"

namespace Constants
{
    const int LengthMin = 4;
    const int LengthMax = 16;

    namespace Default
    {
        const int Length = 8;
        const bool Uppercase = false;
        const bool Lowercase = true;
        const bool Numbers = true;
        const bool Special = false;
        static const QLatin1String SpecialString(".,_+-");
    }

    namespace Settings
    {
        static const QLatin1String Length("Length");
        static const QLatin1String Uppercase("Uppercase");
        static const QLatin1String Lowercase("Lowercase");
        static const QLatin1String Numbers("Numbers");
        static const QLatin1String Special("Special");
        static const QLatin1String SpecialString("SpecialString");
    }

    namespace Charset
    {
        static const QLatin1String Uppercase("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
        static const QLatin1String Lowercase("abcdefghijklmnopqrstuvwxyz");
        static const QLatin1String Numbers("0123456789");
    }
}

// -----------------------------------------------------
class PassGenWidgetPrivate
{
public:
    PassGenWidgetPrivate();
    void save();
    QString generate() const;
    void copy() const;

public:
    QPointer<MSlider> sliderLength;
    QPointer<MLabel> labelLength;
    QPointer<MTextEdit> editSpecial;
    QPointer<MTextEdit> editPassword;
    QPointer<MContentItem> contentItemPassword;
    QPointer<MComboBox> comboBoxHistory;
    QSettings settings;
    MLocale locale;

    // length
    int length;
    bool uppercase;
    bool lowercase;
    bool numbers;
    bool special;
    QString specialString;

private:
    mutable MssfQt::MssfCrypto crypto;
    bool useCrypto;
};

PassGenWidgetPrivate::PassGenWidgetPrivate()
{
    length = settings.value(Constants::Settings::Length, Constants::Default::Length).toInt();
    uppercase = settings.value(Constants::Settings::Uppercase, Constants::Default::Uppercase).toBool();
    lowercase = settings.value(Constants::Settings::Lowercase, Constants::Default::Lowercase).toBool();
    numbers = settings.value(Constants::Settings::Numbers, Constants::Default::Numbers).toBool();
    special = settings.value(Constants::Settings::Special, Constants::Default::Special).toBool();
    specialString = settings.value(Constants::Settings::SpecialString, Constants::Default::SpecialString).toString();

    if (length < Constants::LengthMin)
        length = Constants::LengthMin;
    if (length > Constants::LengthMax)
        length = Constants::LengthMax;

    // use crypto or not
    useCrypto = crypto.initialize();
    srand(time(0));
}

void PassGenWidgetPrivate::save()
{
    settings.setValue(Constants::Settings::Length, length);
    settings.setValue(Constants::Settings::Uppercase, uppercase);
    settings.setValue(Constants::Settings::Lowercase, lowercase);
    settings.setValue(Constants::Settings::Numbers, numbers);
    settings.setValue(Constants::Settings::Special, special);
    settings.setValue(Constants::Settings::SpecialString, specialString);

    // do not need to be done manually
    //settings.sync();
}

QString PassGenWidgetPrivate::generate() const
{
    if (length <= 0)
        return QString();

    // set for possible characters
    QString charset;

    if (uppercase)
        charset.append(Constants::Charset::Uppercase);
    if (lowercase)
        charset.append(Constants::Charset::Lowercase);
    if (numbers)
        charset.append(Constants::Charset::Numbers);
    if (special)
        charset.append(specialString);

    bool fullAscii = false;
    int total = charset.count();
    if (total <= 0)
    {
        total = 0x80 - 0x21;
        fullAscii = true;
    }

    QString password;

    QList<unsigned int> array;

    if (useCrypto)
    {
        QByteArray byte = crypto.random(static_cast<quintptr>(length * 2));

        for (int i = 0; i < length; i++)
        {
            unsigned int c0 = static_cast<unsigned int>(byte[i * 2]);
            unsigned int c1 = static_cast<unsigned int>(byte[i * 2 + 1]);
            unsigned int c = c0 * 0x100 + c1;
            array.append(c);
        }
    }
    else
    {
        for (int i = 0; i < length; i++)
            array.append(static_cast<unsigned int>(rand()));
    }

    while(array.count() > 0)
    {
        unsigned int z = array[0] % total;

        QChar c;
        if (fullAscii)
            c = z + 0x21;
        else
            c = charset[z];

        password.append(c);
        array.removeFirst();
    }

    if (editPassword)
        editPassword->setText(password);

    if (comboBoxHistory)
    {
        comboBoxHistory->insertItem(0, password);
        comboBoxHistory->setEnabled(true);
    }

    return password;
}

void PassGenWidgetPrivate::copy() const
{
    if (!editPassword)
        return;

    editPassword->selectAll();
    editPassword->copy();
}

// -----------------------------------------------------

PassGenWidget::PassGenWidget(QGraphicsItem *parent) :
    MStylableWidget(parent),
    d_ptr(new PassGenWidgetPrivate)
{
    createContent();
}

PassGenWidget::~PassGenWidget()
{
}

void PassGenWidget::createContent()
{
    MLayout *layout = new MLayout();
    layout->setContentsMargins(0, 0, 0, 0);

    MLinearLayoutPolicy *portraitPolicy = new MLinearLayoutPolicy(layout, Qt::Vertical);
    portraitPolicy->setSpacing(0);

    MGridLayoutPolicy *landscapePolicy = new MGridLayoutPolicy(layout);
    landscapePolicy->setSpacing(0);

    // header
    QGraphicsLayoutItem *header = createHeader();
    if (header)
    {
        portraitPolicy->addItem(header);
        landscapePolicy->addItem(header, 0, 0, 1, 2);
    }

    // password
    MContainer *password = createPasswordContainer();
    if (password)
    {
        // the same size like other one
        //password->setPreferredWidth(1);
        portraitPolicy->addItem(password);
        landscapePolicy->addItem(password, 1, 1);
    }

    // settings
    MContainer *settings = createSettingsContainer();
    if (settings)
    {
        // the same size like other one
        //settings->setPreferredWidth(1);
        portraitPolicy->addItem(settings);
        landscapePolicy->addItem(settings, 1, 0);
    }

    // last stretch

    // setup policies to layout
    layout->setLandscapePolicy(landscapePolicy);
    layout->setPortraitPolicy(portraitPolicy);

    setLayout(layout);
}

void PassGenWidget::exitDisplayEvent()
{
//    if (d_ptr->editPassword)
//        d_ptr->editPassword->setEchoMode(MTextEditModel::NoEcho);
}

void PassGenWidget::enterDisplayEvent()
{
//    if (d_ptr->editPassword)
//        d_ptr->editPassword->setEchoMode(MTextEditModel::Normal);
}

QGraphicsLayoutItem *PassGenWidget::createHeader() const
{
    MContainer *container = createEmptyContainer(style()->titleContainerStyleName(), false);

    QGraphicsLinearLayout *layout = new QGraphicsLinearLayout(Qt::Horizontal);
    layout->setContentsMargins(0, 0, 0, 0);
    layout->setSpacing(0);

    MLabel *label = new MLabel();
    label->setStyleName(style()->titleHeaderStyleName());
    label->setText(qtTrId(Loc::AppTitle));

    layout->addItem(label);

    layout->addItem(createSpacer());

    MButton *button = new MButton();
    connect(button, SIGNAL(clicked()), SLOT(clickedInfo()), Qt::QueuedConnection);
    button->setViewType(MButton::iconType);
    button->setIconID(style()->titleInfoIcon());
    button->setStyleName(style()->titleInfoIconStyleName());
    layout->addItem(button);
    layout->setAlignment(button, Qt::AlignRight | Qt::AlignVCenter);

    container->centralWidget()->setLayout(layout);

    return container;
}

MContainer *PassGenWidget::createEmptyContainer(const QString &containerStyle, bool headerVisible) const
{
    MContainer *container = new MContainer;
    container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
    container->setContentsMargins(0, 0, 0, 0);
    container->setStyleName(containerStyle);
    container->setHeaderVisible(headerVisible);

    return container;
}

QGraphicsLayoutItem *PassGenWidget::createSpacer(const QString &style) const
{
    MWidgetController *spacer = new MWidgetController;
    spacer->setViewType(QLatin1String("background"));
    if (!style.isEmpty())
        spacer->setStyleName(style);
    else
        spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    return spacer;
}

QGraphicsLayoutItem *PassGenWidget::createOneSetting(const QString &title, const char *slotName, bool value, QGraphicsLayoutItem *additionalItem)
{
//    MContainer *container = createEmptyContainer();
//    container->setHeaderVisible(false);

    QGraphicsLinearLayout *layout = new QGraphicsLinearLayout(Qt::Horizontal);
    layout->setContentsMargins(0, 0, 0, 0);
    layout->setSpacing(0);

    MLabel *label = new MLabel(title);
    label->setStyleName(style()->settingItemTitleStyleName());

    layout->addItem(label);
    layout->setAlignment(label, Qt::AlignLeft | Qt::AlignVCenter);

    if (additionalItem)
        layout->addItem(additionalItem);

    MButton *button = new MButton();
    button->setViewType(MButton::switchType);
    button->setStyleName(style()->settingItemButtonStyleName());
    button->setCheckable(true);
    button->setChecked(value);
    connect(button, SIGNAL(toggled(bool)), slotName, Qt::UniqueConnection);

    layout->addItem(button);
    layout->setAlignment(label, Qt::AlignRight | Qt::AlignVCenter);

    return layout;

//    container->centralWidget()->setLayout(layout);
//    return container;
}

QGraphicsLayoutItem *PassGenWidget::createLengthSlider()
{
    QGraphicsLinearLayout *layout = new QGraphicsLinearLayout(Qt::Horizontal);
    layout->setContentsMargins(0, 0, 0, 0);
    layout->setSpacing(0);

/*    MLabel *label = new MLabel();
    d_ptr->labelLength = label;
    label->setStyleName(style()->settingItemTitleStyleName());

    layout->addItem(label);*/

    MSlider *slider = new MSlider();
    d_ptr->sliderLength = slider;
    slider->setStyleName(style()->settingItemSliderStyleName());
    slider->setHandleLabelVisible(true);
    modifiedLength(d_ptr->length);
    connect(slider, SIGNAL(valueChanged(int)), this, SLOT(modifiedLength(int)), Qt::UniqueConnection);
    slider->setValue(d_ptr->length);
    slider->setMinimum(Constants::LengthMin);
    slider->setMaximum(Constants::LengthMax);
    slider->setMinLabelVisible(true);
    slider->setMinLabel(d_ptr->locale.formatNumber(slider->minimum()));
    slider->setMaxLabelVisible(true);
    slider->setMaxLabel(d_ptr->locale.formatNumber(slider->maximum()));

    layout->addItem(slider);
    layout->setAlignment(slider, Qt::AlignVCenter | Qt::AlignRight);

    return layout;
}

MContainer *PassGenWidget::createSettingsContainer()
{
    MContainer *container = createEmptyContainer(style()->containerStyleName());
    container->setTitle(qtTrId(Loc::SettingsTitle));

    QGraphicsLinearLayout *layout = new QGraphicsLinearLayout(Qt::Vertical);
    layout->setContentsMargins(0, 0, 0, 0);
    layout->setSpacing(0);

    QGraphicsLayoutItem *slider = createLengthSlider();
    layout->addItem(slider);
    layout->setAlignment(slider, Qt::AlignCenter);

    layout->addItem(createSpacer(style()->settingItemSpacerStyleName()));
    layout->addItem(createOneSetting(qtTrId("ABCDEFGH..."), SLOT(clickedUppercase(bool)), d_ptr->uppercase));
    layout->addItem(createSpacer(style()->settingItemSpacerStyleName()));
    layout->addItem(createOneSetting(qtTrId("abcdefgh..."), SLOT(clickedLowercase(bool)), d_ptr->lowercase));
    layout->addItem(createSpacer(style()->settingItemSpacerStyleName()));
    layout->addItem(createOneSetting(qtTrId("0123456789"), SLOT(clickedNumbers(bool)), d_ptr->numbers));
    layout->addItem(createSpacer(style()->settingItemSpacerStyleName()));

    MTextEdit *edit = new MTextEdit();
    d_ptr->editSpecial = edit;
    connect(edit, SIGNAL(textChanged()), this, SLOT(specialStringChanged()), Qt::UniqueConnection);
    edit->setStyleName(style()->specialCharactersFieldStyleName());
    // disable any predictions etc
    edit->setInputMethodCorrectionEnabled(false);
    edit->setInputMethodAutoCapitalizationEnabled(false);
    edit->setInputMethodPredictionEnabled(false);
    edit->setText(d_ptr->specialString);

    layout->addItem(createOneSetting(qtTrId(Loc::Special), SLOT(clickedSpecial(bool)), d_ptr->special, edit));
    layout->addItem(createSpacer(style()->settingItemSpacerStyleName()));

    container->centralWidget()->setLayout(layout);
    return container;
}

MContainer *PassGenWidget::createPasswordContainer()
{
    MContainer *container = createEmptyContainer(style()->containerStyleName());
    container->setTitle(qtTrId(Loc::PasswordTitle));

    QGraphicsLinearLayout *layout = new QGraphicsLinearLayout(Qt::Vertical);
    layout->setContentsMargins(0, 0, 0, 0);
    layout->setSpacing(0);

//    layout->addItem(createSpacer(style()->passwordSpacerStyleName()));

    MContentItem *contentItem = new MContentItem(MContentItem::TwoTextLabels);
    d_ptr->contentItemPassword = contentItem;
    contentItem->setStyleName(style()->passwordContentItemStyleName());
    contentItem->setTitle(qtTrId(Loc::CurrentPassword));
    //contentItem->setSubtitle(qtTrId("Nothing"));
    updateContent();

    layout->addItem(contentItem);

//    layout->addItem(createSpacer(style()->passwordSpacerStyleName()));

    MTextEdit *edit = new MTextEdit();
    d_ptr->editPassword = edit;
    edit->setReadOnly(true);
    edit->setStyleName(style()->passwordFieldStyleName());

    layout->addItem(edit);
    layout->setAlignment(edit, Qt::AlignCenter);

    layout->addItem(createSpacer(style()->passwordSpacerStyleName()));

    // buttons
    MContainer *buttonContainer = createEmptyContainer(style()->passwordButtonPanelStyleName());
    buttonContainer->setHeaderVisible(false);

    QGraphicsLinearLayout *buttonLayout = new QGraphicsLinearLayout(Qt::Horizontal);
    buttonLayout->setContentsMargins(0, 0, 0, 0);
    buttonLayout->setSpacing(0);

    MButton *buttonCopy = new MButton();
    connect(buttonCopy, SIGNAL(clicked()), SLOT(copyPassword()), Qt::QueuedConnection);
    buttonCopy->setStyleName(style()->passwordCopyStyleName());
    buttonCopy->setText(qtTrId(Loc::Copy));

    buttonLayout->addItem(buttonCopy);

    MButton *buttonGenerate = new MButton();
    connect(buttonGenerate, SIGNAL(clicked()), SLOT(generatePassword()), Qt::QueuedConnection);
    buttonGenerate->setStyleName(style()->passwordGenerateStyleName());
    buttonGenerate->setText(qtTrId(Loc::Create));

    buttonLayout->addItem(buttonGenerate);

    buttonContainer->centralWidget()->setLayout(buttonLayout);
    layout->addItem(buttonContainer);

    // spacer
//    layout->addItem(createSpacer(style()->passwordSpacerStyleName()));

    // history
    MComboBox *comboBox = new MComboBox();
    d_ptr->comboBoxHistory = comboBox;
    comboBox->setStyleName(style()->passwordHistoryStyleName());
    comboBox->setTitle(qtTrId(Loc::History));
    comboBox->setEnabled(false);

    layout->addItem(comboBox);

    // history clear
    MButton *buttonClear = new MButton();
    connect(buttonClear, SIGNAL(clicked()), SLOT(clickedClearHistory()), Qt::QueuedConnection);
    buttonClear->setStyleName(style()->passwordClearStyleName());
    buttonClear->setText(qtTrId(Loc::Clear));

    layout->addItem(buttonClear);
    layout->setAlignment(buttonClear, Qt::AlignCenter);

    container->centralWidget()->setLayout(layout);
    return container;
}

void PassGenWidget::updateContent()
{
    if (!d_ptr->contentItemPassword)
        return;

    QString str = qtTrId(Loc::Length) + QLatin1Char(' ');

    str += d_ptr->locale.formatNumber(d_ptr->length);
    str += QLatin1String(" ");

    if (d_ptr->uppercase)
        str += qtTrId("ABC ");

    if (d_ptr->lowercase)
        str += qtTrId("abc ");

    if (d_ptr->numbers)
        str += qtTrId("123 ");

    if (d_ptr->special)
        str += qtTrId(Loc::Special);

    d_ptr->contentItemPassword->setSubtitle(str);
}

void PassGenWidget::modifiedLength(int value)
{
    if (d_ptr->sliderLength)
    {
        d_ptr->sliderLength->setHandleLabel(d_ptr->locale.formatNumber(value));
    }
    if (d_ptr->labelLength)
    {
        d_ptr->labelLength->setText(d_ptr->locale.formatNumber(value));
    }

    if (value != d_ptr->length)
    {
        qDebug() << "Length value is changed, new" << value;

        d_ptr->length = value;
        d_ptr->save();
        updateContent();
    }
    else
    {
        qDebug() << "Length value is NOT changed, current" << value;
    }
}

void PassGenWidget::clickedUppercase(bool value)
{
    if (value != d_ptr->uppercase)
    {
        qDebug() << "Uppercase value is changed, new" << value;

        d_ptr->uppercase = value;
        d_ptr->save();
        updateContent();
    }
    else
    {
        qDebug() << "Uppercase value is NOT changed, current" << value;
    }
}

void PassGenWidget::clickedLowercase(bool value)
{
    if (value != d_ptr->lowercase)
    {
        qDebug() << "Lowercase value is changed, new" << value;

        d_ptr->lowercase = value;
        d_ptr->save();
        updateContent();
    }
    else
    {
        qDebug() << "Lowecase value is NOT changed, current" << value;
    }
}

void PassGenWidget::clickedNumbers(bool value)
{
    if (value != d_ptr->numbers)
    {
        qDebug() << "Numbers value is changed, new" << value;

        d_ptr->numbers = value;
        d_ptr->save();
        updateContent();
    }
    else
    {
        qDebug() << "Numbers value is NOT changed, current" << value;
    }
}

void PassGenWidget::clickedSpecial(bool value)
{
    if (value != d_ptr->special)
    {
        qDebug() << "Special value is changed, new" << value;

        d_ptr->special = value;
        d_ptr->save();
        updateContent();
    }
    else
    {
        qDebug() << "Special value is NOT changed, current" << value;
    }
}

void PassGenWidget::specialStringChanged()
{
    if (!d_ptr->editSpecial)
        return;

    QString text = d_ptr->editSpecial->text();
    if (text != d_ptr->specialString)
    {
        d_ptr->specialString = text;
        d_ptr->save();
        updateContent();
    }
}

void PassGenWidget::generatePassword()
{
    d_ptr->generate();
}

void PassGenWidget::copyPassword()
{
    d_ptr->copy();
}

void PassGenWidget::clickedClearHistory()
{
    if (d_ptr->editPassword)
        d_ptr->editPassword->setText(QString());

    if (!d_ptr->comboBoxHistory)
        return;

    d_ptr->comboBoxHistory->clear();
    d_ptr->comboBoxHistory->setEnabled(false);
}

#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)


void PassGenWidget::clickedInfo()
{
    MMessageBox box(qtTrId(Loc::AppTitle) + QLatin1Char(' ') + QLatin1String(TOSTRING(APPVER)), QString::fromUtf8("© 2012 Denis Mingulov"));
    box.exec();
}
